package routing.googleadapter;


import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.PolylineOptions;

import de.rose_mobil.models.Coordinates;
import de.rose_mobil.models.CustomPlacemark;
import de.rose_mobil.models.route.Route;
import de.rose_mobil.models.route.RoutePart;



/**
 * represents a KML-file from Google and is able to parse it
 * into a vector of Placemarks and to a JSON string
 * 
 * @author Alexander Münch (sialmuen)
 */
public class GoogleKMLParser extends DefaultHandler {
	
	private String kmlData;
	private Route footway;
	
	/**
	 * @param xmlKmlData: full XML data
	 */
	public GoogleKMLParser(String xmlKmlData) {
		kmlData = xmlKmlData;
		footway = null;
	}

	
	/**
	 * returns a vector of KmlPlacemarks
	 * @return Vector of KmlPlacemarks
	 */
	public Route getWaypoints() {
		if(footway == null)
			parse();
		
		return footway;
	}
	
	
	/**
	 * parses the data
	 */
	private void parse() {		
		DocumentBuilder dBuilder;
		Document document;
		ArrayList<RoutePart> routeParts = new ArrayList<RoutePart>();
		RoutePartWalkGoogle currentPart;
		
		try {
			dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			document = dBuilder.parse(new InputSource(new StringReader(kmlData)));
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		}

		NodeList placemarkNodes = document.getElementsByTagName("step");
		for(int i = 0; i < placemarkNodes.getLength(); i++) {
			Node placemarkNode = placemarkNodes.item(i);

			String description = "";
			Coordinates coordinates = null;
			CustomPlacemark c;

			currentPart = new RoutePartWalkGoogle();
			routeParts.add(currentPart);
			
			if(placemarkNode.hasChildNodes()) {
				Node child = placemarkNode.getFirstChild();
				do {
					String tagName = child.getNodeName();

					if (tagName.equals("start_location")) {
						coordinates = getCoordinates(child.getFirstChild());
						c = new CustomPlacemark(tagName, coordinates);
						currentPart.setFromPos(c);
					}
					else if (tagName.equals("end_location")) {
						coordinates = getCoordinates(child.getFirstChild());
						c = new CustomPlacemark(tagName, coordinates);
						currentPart.setToPos(c);
					}
					else if (tagName.equals("html_instructions")) {
						description = child.getTextContent();
						currentPart.addExtraData(description);
					} 
					else if (tagName.equals("duration")) {
						Node durData = child.getFirstChild();
						while ((durData != null) && !durData.getNodeName().equals("value")) durData = durData.getNextSibling();
						
						if (durData != null) currentPart.setWalkTimeNeeded(Long.parseLong(durData.getFirstChild().getNodeValue()));
					}
					else if (tagName.equals("distance")) {
						Node durData = child.getFirstChild();
						while ((durData != null) && !durData.getNodeName().equals("value")) durData = durData.getNextSibling();
						
						if (durData != null) currentPart.setDistance(Long.parseLong(durData.getFirstChild().getNodeValue()));
					}
					else if (tagName.equals("polyline")) {
						Node points = child.getFirstChild();
						if (points.getNodeName().equals("points")) 
							currentPart.setLineData(decodePoly(points.getFirstChild().getNodeValue()));
					}
					
					child = child.getNextSibling();
				} while(child != null);
			}
		}
		
		RoutePart [] parts = new RoutePart[routeParts.size()];
		int i = 0;
		for (RoutePart rp : routeParts) {
			parts[i++] = rp;
		}
		
		footway = new Route();
		footway.setRouteParts(parts);
	}
	
	private PolylineOptions decodePoly(String encoded) {
		PolylineOptions poly = new PolylineOptions();
		int index = 0, len = encoded.length();
		int lat = 0, lng = 0;

		while (index < len) {
			int b, shift = 0, result = 0;
			do {
				b = encoded.charAt(index++) - 63;
				result |= (b & 0x1f) << shift;
				shift += 5;
			} while (b >= 0x20);
			int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
			lat += dlat;

			shift = 0;
			result = 0;
			do {
				b = encoded.charAt(index++) - 63;
				result |= (b & 0x1f) << shift;
				shift += 5;
			} while (b >= 0x20);
			int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
			lng += dlng;

			LatLng p = new LatLng((((double) lat / 1E5)),(((double) lng / 1E5)));
			poly.add(p);
		}

		return poly;
	}
	
	private Coordinates getCoordinates(Node pointChild) {
		double lat = 0, lng = 0;
		
		while(pointChild != null) {
			if(pointChild.getNodeName().equals("lat")) lat = Double.valueOf(pointChild.getTextContent());
			else if(pointChild.getNodeName().equals("lng")) lng = Double.valueOf(pointChild.getTextContent());

			pointChild = pointChild.getNextSibling();
		}
		
		return new Coordinates(lat, lng);
	}
	
	/**
	 * returns all route parts as a JSON array
	 * @return String JSON array
	 */
	
	public String toJSON() {
		if (footway != null) return footway.toString();
		else return new Route().toString();
	}
}
